#ifndef     __CONN_PORT_H
#define     __CONN_PORT_H

#include "../utils/byte_buf.h"
#include <QString>
#include <coroutine>
#include "./conn_impl.h"
#include "../utils/message_queue.h"
#include <memory>
#include <QObject>
#include "../utils/nocopyable.h"
#include "./resp.h"
#include "../rt/task2.h"

namespace conn {

class ConnPort : public QObject, private utils::NoCopyable {
    Q_OBJECT
public:
    ConnPort();
    ~ConnPort();

    void open(const QString &name, int baud_rate = 115200);
    void close();

    void write(utils::ByteBuf&& buf) { m_write_mq.put(std::move(buf)); }

    rt::Task2<> ping() {
        uint8_t seq = get_seq();
        auto fut = m_resp_map.pong(seq, 1000);
        write( make_proto( proto::type::PING, seq ) );
        co_await fut;
    }

    template <typename... Ts>
    rt::Task2< Resp > simple_req(uint8_t cmd, const Ts&... ts) {
        uint8_t seq = get_seq();
        auto fut = m_resp_map.resp(proto::type::SIMPLE_RES, seq, 1000);
        write( make_proto( proto::type::SIMPLE_REQ, seq, cmd, ts... ) );
        utils::ByteBuf buf = co_await fut;
        co_return Resp(std::move(buf));
    }

    template <typename... Ts>
    rt::Task2< Resp > req(uint8_t cmd, const Ts&... ts) {
        co_await session();

        uint8_t seq = get_seq();
        auto fut = m_resp_map.resp(proto::type::RES, seq, 3000);
        write( make_proto(proto::type::REQ, seq, cmd, ts...) );
        
        for (;;) {

            try {
                utils::ByteBuf buf = co_await fut;
                co_return Resp(std::move(buf));
            } catch (const rt::TimeoutError &err) {
                bool ret = co_await check_task(seq, cmd);
                if (!ret) {
                    utils::ByteBuf buf = co_await fut;
                    co_return Resp(std::move(buf));
                }
            }
        }
    }

signals:
    void notify( std::shared_ptr<utils::ByteBuf> buf );

private:
    rt::Task2<> session() {
        uint8_t seq = get_seq();
        auto fut = m_resp_map.ack(seq, 1000);
        write( make_proto(proto::type::SESSION, seq) );
        co_await fut;
    }

    rt::Task2<bool> check_task(uint8_t seq, uint8_t cmd) {
        Resp resp = co_await simple_req(proto::cmd::GET_TASK_INFO, seq, cmd);
        uint8_t cmd2, seq2;
        resp.parse(proto::cmd::GET_TASK_INFO, seq2, cmd2);
        co_return (seq == seq2) && (cmd == cmd2);
    }
    
    void write_ack(uint8_t seq) { write( make_proto(proto::type::ACK, seq) ); }
    void write_pong(uint8_t seq) { write( make_proto(proto::type::PONG, seq) ); }

    void recv_task();
    void write_task();
    void dispatch(utils::ByteBuf&& buf);
private:
    utils::MessageQueue<utils::ByteBuf> m_write_mq;
    RespMap m_resp_map;

    std::thread m_reader;
    std::thread m_writer;

    asio::io_context m_ctx;
    std::unique_ptr<asio::serial_port> m_port;
    uint8_t m_seq = 0;

    uint8_t get_seq() {
        uint8_t v = m_seq;
        m_seq += 1;
        return v;
    }
};

}

#endif
